﻿using System;
using System.IO;
using System.Reflection;

namespace BigfootMVC.DNN
{
    public class RouteHelper
    {

        /// <summary>
        /// Probes the different view paths to find the control
        /// 1. Controller and ViewName is empty then it defaults to home/default.ascx
        /// 2. Controller is empty then it defaults to home/
        /// 3. ViewName is empty then it defaults Index.ascx
        /// 4. If ViewName is not found in the Controller path specified, then it tries to find it in shared/ path under the views directory
        /// </summary>
        /// <param name="controller">
        /// The folder name that contains the view. Can include a full relative path to the file, or a full relative path to the folder that contains this view
        /// i.e. ~/Reports/Employees/EmployeeReport.ascx --> Used literally
        ///      ~/Reports/Employees --> Translates to ~/Reports/Employees/[viewName]
        ///      Orders --> Transalates to ~/Views/Orders/[viewName]
        ///      Orders/Test --> Translates to ~/Views/Orders/Test/[viewName]
        /// </param>
        /// <param name="viewName">
        /// The ViewName within the section. 
        ///     i.e. AddEmployee --> AddEmployee.ascx
        ///          AddEmployee.ascx --> Used literally
        ///          AddEmployee.ashx --> Used literally
        /// </param>
        /// <param name="app">This is the DnnApp object created by your module and attached to the RouteInfo object. It is used in order identify the module paths</param>
        /// <returns>The application relative path to the control</returns>
        public static string ControlPath(string controller, string viewName, DnnApp app)
        {
            // Default to Home for the controller and Index for the viewName
            if (string.IsNullOrEmpty(controller)) controller = "Home";
            if (string.IsNullOrEmpty(viewName)) viewName = "index";

            // Determine weather the user is specifying the full controller path
            var ControllerIsRelativePath = controller.StartsWith("~/");
            var ControllerIncludesViewName = controller.EndsWith(".ascx") || controller.EndsWith("ashx");

            // Add the / slash to the end of the controller's folder name. Do not do this if the controller includes the view name
            if (!ControllerIncludesViewName && !controller.EndsWith("/")) controller += "/";

            // Make sure the viewName has the .ascx at the end
            if (!viewName.EndsWith(".ascx") && !viewName.EndsWith(".ashx")) viewName += ".ascx";

            // Do not search in the shared folder if the folderName is a full relative path
            if (ControllerIncludesViewName)
            {
                if (File.Exists(app.Server.MapPath(controller)))
                    return controller;
                else
                    throw new ApplicationException(String.Format("Unable to find control {0}", controller));
            }

            // Do not append the global Views path when the controller includes a relative path
            if (ControllerIsRelativePath)
            {
                if (File.Exists(app.Server.MapPath(controller + viewName)))
                {
                    return controller + viewName;
                }
                else if (File.Exists(app.Server.MapPath(controller + "Shared/" + viewName)))
                {
                    return controller + "Shared/" + viewName;
                }
                else
                {
                    throw new ApplicationException(String.Format(app.MVCStrings.UnableToFindControl,
                                                                 controller + viewName,
                                                                 controller + "Shared/" + viewName));
                }
            }

            // Search first the natural location. Then try the shared location
            if (File.Exists(app.Server.MapPath(app.ViewsPath + controller + viewName)))
            {
                return app.ViewsPath + controller + viewName;
            }
            else if (File.Exists(app.Server.MapPath(app.ViewsPath + "Shared/" + viewName)))
            {
                return app.ViewsPath + "Shared/" + viewName;
            }
            else
            {
                throw new ApplicationException(String.Format(app.MVCStrings.UnableToFindControl,
                                                             app.ViewsPath + controller + viewName,
                                                             app.ViewsPath + "Shared/" + viewName));
            }
        }


        /// <summary>
        /// Executes an action and returns the action result
        /// </summary>
        /// <param name="route">The route to execute</param>
        public static ActionResult ExecuteAction(RouteInfo route)
        {
            // Normalize the namespace
            var ns = route.App.ModuleNamespace;
            if (string.IsNullOrEmpty(route.App.ModuleNamespace) != true) ns += ".";

            // Add the controllers namespace
            ns += "Controllers.";

            // Get the literals to use
            var typename =ns + route.Controller + "Controller";
            var controllerKey = typename.ToLower() + ":ControllerCache";
            var actionKey = controllerKey + route.Action.ToLower() + ":ActionCache";

            // CONTROLLER
            //      Get the type from the cache, if not there add it
            var objT = (Type) WebApp.Cache.GetValue(controllerKey);
            if (objT == null)
            {
                //var asm = Assembly.GetEntryAssembly();
                //if (asm != null)
                //{
                    //objT = asm.GetType(typename, true, true);
                objT = Type.GetType(typename + ", " + route.App.ModuleAssemblyName);
                if (objT != null) WebApp.Cache.Add(controllerKey, objT);
                //}
            }
            //      Ensure the controller was found
            if (objT == null) throw new ApplicationException(string.Format(route.App.MVCStrings.ControllerNotFound, typename ,route.App.ModuleAssemblyName ));
            //      Construct the controller and set the route information
            var objController = (BaseController)objT.InvokeMember("ctor", BindingFlags.CreateInstance, null, null, null) ;
            objController.Route = route;
            // ACTION
            //      Get the method from the cache. If not there then get it and add it
            var cachedMethod = (MethodInfo) WebApp.Cache.GetValue(actionKey);
            if (cachedMethod == null)
            {
                foreach (var m in objT.GetMethods())
                {
                    if (m.Name.ToLower() == route.Action.ToLower())
                    {
                        WebApp.Cache.Add(actionKey,m);
                        cachedMethod = m;
                        break;
                    }
                }
            }
            //      Ensure the method exists
            if (cachedMethod == null) throw new ApplicationException(string.Format(route.App.MVCStrings.ActionNotFound, typename, route.Action));
            //      Execute the action
            return (ActionResult)cachedMethod.Invoke(objController, null);
        }

    }
}
